/* 
 * File:   RepositorioPoller.cpp
 * Author: maximiliano
 * 
 * Created on May 14, 2013, 6:05 PM
 */

#include <vector>

#include "client_RepositorioPoller.h"
#include "common_MutexScopeLocker.h"
#include "common_Logger.h"
#include "tests/RepositorioPollerManejadorEvetoDummy.h"

RepositorioPoller::RepositorioPoller(Repositorio* pRepositorio, uint32_t pIntervaloPoleo) {
    _intervaloPoleo = pIntervaloPoleo;
    _repositorio = pRepositorio;
    _poleando = false;
    manejadorEvento = NULL;
}

void RepositorioPoller::SetRepositorioPollerManejadorEvento (IRepositorioPollerManejadorEvento* pManejadorEventos){
    this->manejadorEvento = pManejadorEventos;
}

void RepositorioPoller::SetOperacionSincronizacionRepositorio (OperacionSincronizacionRepositorio* pOperacionSincrnizacionRepositorio){    
   _operacionSincronizacionRepositorio = pOperacionSincrnizacionRepositorio;
   _operacionSincronizacionRepositorio->setRepositorioLocal(_repositorio);
}

void RepositorioPoller::SetOperacionSincronizacionInicialRepositorio (OperacionSincronizacionInicialRepositorio* pOperacionSincrnizacionRepositorio){    
   _operacionSincronizacionInicialRepositorio = pOperacionSincrnizacionRepositorio;
   _operacionSincronizacionInicialRepositorio->setRepositorioLocal(_repositorio);
}

RepositorioPoller::~RepositorioPoller() {}

void RepositorioPoller::Iniciar() throw (Excepcion&){

    try
    {
        FileUtils f_utils;
        
        if (!f_utils.DirectorioExistente (this->_repositorio->GetPath()))
        {
            string msg = "Fallo al iniciar el poller.  Directorio \" ";
            msg += this->_repositorio->GetPath();
            msg += "\" inexistente.";
            
            throw Excepcion (msg);
        }
        
        /* 
         * TODO: 
         *  - Pasarle al crear el poller el repositorio del servidor
         *    (ver Cliente::ObtenerRepositorioServidor)
         * 
         *  - Realizar la sincronizacion de la carpeta AU contra dicho repositorio
         *  - Setear ese repositorio como repositorio inicial del poller
         */
        
        setPoleando(true);
        
        Thread::Iniciar();
    }
    catch (Excepcion &ex)
    {
        throw ex;
    }
}

void RepositorioPoller::Finalizar() throw (Excepcion&){
    
    try
    {
        if (getPoleando())
        {
            setPoleando(false);

            Thread::Finalizar();
        }
    }
    catch (Excepcion &ex)
    {
        throw ex;
    }
}

void RepositorioPoller::tarea (){
    
    /* La operacion de sincronizacion inicial tiene como fin sincronizar los
     * repositorios cliente-servidor antes de iniciar el proceso de polling.
     * 
     */
    _operacionSincronizacionInicialRepositorio->ejecutar();
    
    /* Se debe cambiar la operacion de sincronizacion inicial del repositorio
     * por la operacion de sincronizacion subsecuente.
     */
    
    while(_poleando)
    {        
        if (!_operacionSincronizacionInicialRepositorio->finalizada()) continue;
        
        if (!_operacionSincronizacionRepositorio->finalizada()) continue;
        
        /* Se ejecuta la operacion de sincronizacion de repositorios cliente
         * servidor.
         */
        this->ActualizarRepositorio();
        
        // Se ejecuta la tarea de sincronizacion subsecuente.
        _operacionSincronizacionRepositorio->ejecutar();
        
        usleep(this->_intervaloPoleo);
    }
}

void RepositorioPoller::ActualizarRepositorio(){

    LNombreArchivo lista_inicial;

    if (!_fileUtils.ObtenerNombreArchivos(this->_repositorio->GetPath(), lista_inicial))
    {
        /* No se puede acceder al repositorio fisico.  Se deja constancia del
         * error en el log y se finaliza la operaciond e chequeo de repositorio.
         */
        Logger::LogMessage("No se puede acceder al repositorio. "
                           "Ruta inexistente (" + this->_repositorio->GetPath()
                            + ")."
        );
        setPoleando(false);
    }

    /* Se evalua el archivo a fin de determinar si es un archivo nuevo o si 
     * el mismo fue modificado.
     */
    LNombreArchivo::iterator it;

    for(it = lista_inicial.begin(); it != lista_inicial.end(); it++)
    {       
        evaluarArchivo(*it);
    }

    /* Se verifica si se eliminaron archivos.  De ser así, se actualiza el 
     * estado del archivo del repositorio.
     */
    this->actualiarEstadoArchivosEliminados();
}

void RepositorioPoller::evaluarArchivo(const string& pNombreArchivo){
    
    if (esArchivoNuevo(pNombreArchivo))
    {
        /* Creo el RepoItem correspondiente, lo configuro con la informacion
         * del archivo y lo agrego al repositorio.
         */
        FileUtils fileUtils;

        RepoItem* item = _repositorio->GetRepoItemByFilename(pNombreArchivo);

        if (NULL == item)
        {
            item = _repositorio->CrearItem();

            item->SetEstado(ARCHIVO_A_PUBLICAR);
            item->SetFilename(pNombreArchivo);

            for (size_t i = 0; i < fileUtils.CalcularCantidadChunks(item->GetRutaCompletaArchivo()); i++)
            {
                item->AddHash(fileUtils.GetHash(_repositorio->GetPath() + pNombreArchivo, i));
            }

            _repositorio->AgregarRepoItem(item);
        }
        else if (item->GetEstado() == ARCHIVO_DETECTADO)
        {
            item->SetEstado(ARCHIVO_A_PUBLICAR);
        }
        
        /* Actualizo la version del repositorio.
         */
        _repositorio->SetVersion(_repositorio->GetVersion() +1);
        
        this->notificarArchivoNuevo(pNombreArchivo);
    }
    else if (esArchivoModificado(pNombreArchivo))
    {
        /* Actualizar la lista de hashes, esto es, eliminar los hashes actuales
         * y recalcularlos.
         */
        RepoItem* item = _repositorio->GetRepoItemByFilename(pNombreArchivo);
        
        item->SetEstado(ARCHIVO_MODIFICADO);
        item->LimpiarHashes();
        
        
        for (size_t i = 0; i < _fileUtils.CalcularCantidadChunks(item->GetRutaCompletaArchivo()); i++)
        {
            item->AddHash(_fileUtils.GetHash(item->GetRutaCompletaArchivo(), i));
        }
        
        /* Se actualiza la version del archivo.
         */
        item->SetVersion(item->GetVersion() + 1);
        
        /* Se actualiza la version del repositorio.
         */
        _repositorio->SetVersion(_repositorio->GetVersion() +1);
        
        this->notificarArchivoModificado(pNombreArchivo);
    }
}

void RepositorioPoller::setPoleando(const bool pValor){
    
    MutexScopeLocker _locker (_poleandoMutex);
    _poleando = pValor;
}

bool RepositorioPoller::getPoleando(){
    MutexScopeLocker _locker (_poleandoMutex);
    return _poleando;
}

bool RepositorioPoller::esArchivoNuevo(const string &pNombreArchivo){

    /* Se asume que pNombreArchivo es el nombre de un archivo alojado en el
     * el repositorio AU.
     * 
     * Un archivo es nuevo cuando figura fisicamente en el directorio AU pero no 
     * figura en el repositorio.
     * 
     */
    RepoItem* item = _repositorio->GetRepoItemByFilename(pNombreArchivo);
    
    if (NULL == item ) return true;

    return (item->GetEstado() == ARCHIVO_A_PUBLICAR);
}

bool RepositorioPoller::esArchivoModificado(const string &pNombreArchivo){

    /* Se asume que pNombreArchivo es el nombre de un archivo alojado en el
     * repositorio AU.
     * 
     * Un archivo es modificado si para alguno de sus chunks calculados, el hash
     * difiere del hash asociado al chunk según el repositorio.
     * 
     */
    
    RepoItem* item = _repositorio->GetRepoItemByFilename(pNombreArchivo);
    
    string rutaCompletaArchivo = item->GetRutaCompletaArchivo ();
    size_t cantidadChunks = _fileUtils.CalcularCantidadChunks(rutaCompletaArchivo);
    bool modificado = (item->GetCantidadHashes() !=  cantidadChunks);
    
    for (size_t i = 0; i <  cantidadChunks && !modificado; i++)
    {
        modificado = (item->GetHash(i) != _fileUtils.GetHash (rutaCompletaArchivo, i));
    }

    return modificado;
}

void RepositorioPoller::notificarArchivoNuevo (const string &pNombreArchivo){

    if (NULL != manejadorEvento)
    {
        manejadorEvento->archivoNuevoEvento(pNombreArchivo);
    }
}

void RepositorioPoller::notificarArchivoModificado (const string &pNombreArchivo){
    
    if (NULL != manejadorEvento)
    {
        manejadorEvento->archivoModificadoEvento(pNombreArchivo);
    }
}

void RepositorioPoller::actualiarEstadoArchivosEliminados (){

    /* Para todo item en el repositorio, se evalua si el archivo correspondiente
     * fue eliminado.  Esto es, fugura un item en el repositorio cuando en el
     * direcotrio no figura el archivo correspondiente.
     * 
     */    

    RepoItem* item = _repositorio->Primero();
    bool existenArchivosEliminados = false;
    
    while (NULL != item)
    {
        bool eliminando = ARCHIVO_A_ELIMINANDO_EN_SERVIDOR == item->GetEstado();
        bool archivoExistente = _fileUtils.ArchivoExistente(item->GetRutaCompletaArchivo());
        
        if ( !eliminando && !archivoExistente )
        {
            item->SetVersion(item->GetVersion() + 1);
            item->SetEstado(ARCHIVO_A_ELIMINAR_EN_SERVIDOR);
            
            existenArchivosEliminados = true;
        }

        item = _repositorio->Siguiente(item);
    }
    
    if (existenArchivosEliminados)
    {
        _repositorio->SetVersion(_repositorio->GetVersion() +1);
    }
}
